#include "Subsystems/BlockBehaviors/SubsystemMusketBlockBehavior.h"
#include "Components/ComponentMiner.h"
#include "Components/ComponentCreature.h"
#include "Components/ComponentCreatureModel.h"
#include "Components/ComponentBody.h"
#include "Components/ComponentPlayer.h"
#include "Components/ComponentLevel.h"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemProjectiles.h"
#include "Blocks/MusketBlock.h"
#include "Blocks/GunpowderBlock.h"
#include "Blocks/CottonWadBlock.h"
#include "Blocks/BulletBlock.h"
#include "Log.h"
#include "GameRandom.hpp"
#include "GameSingleton.h"

namespace PocketSurvival
{

    SubsystemMusketBlockBehavior::SubsystemMusketBlockBehavior()
    {
    }

    void SubsystemMusketBlockBehavior::setAimTime(ComponentMiner *componentMiner, double time)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        aimStartTimes[componentMiner] = time;
    }

    void SubsystemMusketBlockBehavior::removeAimTime(ComponentMiner *componentMiner)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        auto it = aimStartTimes.find(componentMiner);
        if (it != aimStartTimes.end())
        {
            aimStartTimes.erase(it);
        }
    }

    double SubsystemMusketBlockBehavior::getAimTime(ComponentMiner *componentMiner)
    {
        std::lock_guard<std::mutex> lk(m_mapMutex);
        auto it = aimStartTimes.find(componentMiner);
        if (it != aimStartTimes.end())
        {
            return it->second;
        }
        return -1;
    }

    void SubsystemMusketBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(15.0, 0.5) == true)
        {
            std::lock_guard<std::mutex> lk(m_mapMutex);
            bool needChange = false;
            double gameTime = m_timeOfDay->getGameTime();
            for (auto it = aimStartTimes.begin(); it != aimStartTimes.end(); ++it)
            {
                if (gameTime - it->second > 15.0)
                {
                    needChange = true;
                    break;
                }
            }
            if (needChange == true)
            {
                std::unordered_map<ComponentMiner *, double> aimTimeMap;
                for (auto it = aimStartTimes.begin(); it != aimStartTimes.end(); ++it)
                {
                    if (gameTime - it->second < 15.0)
                    {
                        aimTimeMap[it->first] = it->second;
                    }
                }
                aimStartTimes.swap(aimTimeMap);
            }
        }
    }

    bool SubsystemMusketBlockBehavior::onAim(const Ray3 &aim, ComponentMiner *componentMiner, AimState state)
    {
        IInventory *inventory = componentMiner->inventory;
        if (inventory != nullptr)
        {
            int32_t activeSlotIndex = inventory->getActiveSlotIndex();
            if (activeSlotIndex >= 0)
            {
                int32_t slotValue = inventory->getSlotValue(activeSlotIndex);
                int32_t slotCount = inventory->getSlotCount(activeSlotIndex);
                int32_t num = Terrain::ExtractContents(slotValue);
                int32_t data = Terrain::ExtractData(slotValue);
                int32_t num2 = slotValue;
                int32_t num3 = 0;
                if (num == MusketBlock::Index() && slotCount > 0)
                {
                    double value = getAimTime(componentMiner);
                    if (value < 0.0)
                    {
                        value = m_timeOfDay->getGameTime();
                        setAimTime(componentMiner, value);
                    }

                    float num4 = (float)(m_timeOfDay->getGameTime() - value);
                    float num5 = (float)MathUtils::Remainder(m_timeOfDay->getGameTime(), 1000.0);

                    switch (state)
                    {
                    case AimState::InProgress:
                    {
                        if (num4 >= 10.0f)
                        {
                            return true;
                        }
                        if (num4 > 0.5f && false == MusketBlock::GetHammerState(Terrain::ExtractData(num2)))
                        {
                            num2 = Terrain::MakeBlockValue(num, 0, MusketBlock::SetHammerState(Terrain::ExtractData(num2), true));
                        }
                    }
                    break;
                    case AimState::Cancelled:
                    {
                        if (MusketBlock::GetHammerState(Terrain::ExtractData(num2)))
                        {
                            num2 = Terrain::MakeBlockValue(num, 0, MusketBlock::SetHammerState(Terrain::ExtractData(num2), false));
                        }
                        removeAimTime(componentMiner);
                    }
                    break;
                    case AimState::Completed:
                    {
                        bool flag = false;
                        int32_t value2 = 0;
                        int32_t num6 = 0;
                        float s = 0.0f;
                        Vector3 vector = FastVector3::Zero;
                        MusketLoadState loadState = MusketBlock::GetLoadState(data);
                        BulletType bulletType = MusketBlock::GetBulletType(data);

                        if (MusketBlock::GetHammerState(Terrain::ExtractData(num2)))
                        {
                            switch (loadState)
                            {
                            // case MusketLoadState::Empty:
                            // 	break;
                            case MusketLoadState::Gunpowder:
                            case MusketLoadState::Wad:
                                flag = true;
                                break;
                            case MusketLoadState::Loaded:
                                flag = true;
                                if (bulletType == BulletType::Buckshot)
                                {
                                    value2 = Terrain::MakeBlockValue(BulletBlock::Index(), 0, BulletBlock::SetBulletType(0, BulletType::BuckshotBall));
                                    num6 = 8;
                                    vector.posX = 0.04f;
                                    vector.posY = 0.04f;
                                    vector.posZ = 0.25f;
                                    s = 80.0f;
                                }
                                else if (bulletType == BulletType::BuckshotBall)
                                {
                                    value2 = Terrain::MakeBlockValue(BulletBlock::Index(), 0, BulletBlock::SetBulletType(0, BulletType::BuckshotBall));
                                    num6 = 1;
                                    vector.posX = 0.06f;
                                    vector.posY = 0.06f;
                                    vector.posZ = 0.0f;
                                    s = 60.0f;
                                }
                                else if (bulletType != BulletType::None)
                                {
                                    value2 = Terrain::MakeBlockValue(BulletBlock::Index(), 0, BulletBlock::SetBulletType(0, BulletType::MusketBall));
                                    num6 = 1;
                                    s = 120.0f;
                                }

                                // Log::info(fmt::format("Loaded value: {}, num6: {}", value2, num6));

                                break;
                            }
                        }
                        if (flag)
                        {
                            if (componentMiner->componentCreature->componentBody->immersionFactor < 0.4f)
                            {
                                // Log::info("en???");

                                const Matrix &matrix = componentMiner->componentCreature->componentBody->getMatrix();

                                Vector3 vector2 = componentMiner->componentCreature->componentCreatureModel->getEyePosition() + matrix.getRight() * 0.3f - matrix.getUp() * 0.2f + matrix.getForward() * 0.65f;
                                Vector3 vector3 = Vector3::Normalize(vector2 + aim.Direction * 10.0f - vector2);
                                Vector3 vector4 = Vector3::Normalize(Vector3::Cross(vector3, FastVector3::UnitY));
                                Vector3 v2 = Vector3::Normalize(Vector3::Cross(vector3, vector4));
                                for (int32_t i = 0; i < num6; i++)
                                {
                                    Vector3 v3 = GameSingleton::gameRandom->getFloat(0.0f - vector.posX, vector.posX) * vector4 + 
                                                 GameSingleton::gameRandom->getFloat(0.0f - vector.posY, vector.posY) * v2 + 
                                                 GameSingleton::gameRandom->getFloat(0.0f - vector.posZ, vector.posZ) * vector3;

                                    Projectile *projectile = m_projectiles->fireProjectile(value2, vector2, s * (vector3 + v3), FastVector3::Zero, componentMiner->componentCreature);
                                    if (projectile != nullptr)
                                    {
                                        projectile->projectileStoppedAction = ProjectileStoppedAction::Disappear;
                                    }
                                }
                                componentMiner->componentCreature->componentBody->applyImpulse(-4.0f * vector3);
                            }
                            num2 = Terrain::MakeBlockValue(Terrain::ExtractContents(num2), 0, MusketBlock::SetLoadState(Terrain::ExtractData(num2), MusketLoadState::Empty));
                            num3 = 1;
                        }
                        if (MusketBlock::GetHammerState(Terrain::ExtractData(num2)))
                        {
                            num2 = Terrain::MakeBlockValue(Terrain::ExtractContents(num2), 0, MusketBlock::SetHammerState(Terrain::ExtractData(num2), false));
                        }

                        removeAimTime(componentMiner);

                        if (num2 != slotValue)
                        {
                            inventory->removeSlotItems(activeSlotIndex, 1);
                            inventory->addSlotItems(activeSlotIndex, num2, 1);
                        }
                        if (num3 > 0)
                        {
                            componentMiner->damageActiveTool(num3);
                        }
                        return true;
                    }
                    break;
                    }

                    if (num2 != slotValue)
                    {
                        inventory->removeSlotItems(activeSlotIndex, 1);
                        inventory->addSlotItems(activeSlotIndex, num2, 1);
                    }
                    if (num3 > 0)
                    {
                        componentMiner->damageActiveTool(num3);
                    }
                }
            }
        }
        return false;
    }

    uint32_t SubsystemMusketBlockBehavior::getProcessInventoryItemCapacity(IInventory *inventory, uint32_t slotIndex, int32_t cellValue)
    {
        int32_t num = Terrain::ExtractContents(cellValue);
        MusketLoadState loadState = MusketBlock::GetLoadState(Terrain::ExtractData(inventory->getSlotValue(slotIndex)));
        if (loadState == MusketLoadState::Empty && num == GunpowderBlock::Index())
        {
            return 1;
        }
        if (loadState == MusketLoadState::Gunpowder && num == CottonWadBlock::Index())
        {
            return 1;
        }
        if (loadState == MusketLoadState::Wad && num == BulletBlock::Index())
        {
            return 1;
        }
        return 0;
    }

    void SubsystemMusketBlockBehavior::processInventoryItem(IInventory *inventory, uint32_t slotIndex, int32_t cellValue, uint32_t count, uint32_t processCount, int32_t &outProcessedValue, uint32_t &outProcessedCount)
    {
        outProcessedValue = cellValue;
        outProcessedCount = count;
        if (processCount == 1)
        {
            int32_t data = Terrain::ExtractData(inventory->getSlotValue(slotIndex));
            MusketLoadState loadState = MusketBlock::GetLoadState(data);
            BulletType bulletType = MusketBlock::GetBulletType(data);
            switch (loadState)
            {
            case MusketLoadState::Empty:
                loadState = MusketLoadState::Gunpowder;
                bulletType = BulletType::None;
                break;
            case MusketLoadState::Gunpowder:
                loadState = MusketLoadState::Wad;
                bulletType = BulletType::None;
                break;
            case MusketLoadState::Wad:
            {
                loadState = MusketLoadState::Loaded;
                int32_t data2 = Terrain::ExtractData(cellValue);
                bulletType = BulletBlock::GetBulletType(data2);
                break;
            }
            }
            outProcessedValue = 0;
            outProcessedCount = 0;
            inventory->removeSlotItems(slotIndex, 1);
            inventory->addSlotItems(slotIndex, Terrain::MakeBlockValue(MusketBlock::Index(), 0, MusketBlock::SetBulletType(MusketBlock::SetLoadState(data, loadState), bulletType)), 1);
        }
    }

    static const std::string subsystemName = "MusketBlockBehavior";
    const std::string &SubsystemMusketBlockBehavior::getName() const
    {
        return subsystemName;
    }

    void SubsystemMusketBlockBehavior::load(const nlohmann::json &json)
    {
        m_projectiles = GameSingleton::singletonPtr->getSingleton<SubsystemProjectiles>();
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
    }
    SubUpdateType SubsystemMusketBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
    
}